package datart.data.provider;

import com.google.common.collect.ImmutableList;
import datart.core.base.consts.ValueType;
import datart.core.data.provider.ExecuteParam;
import datart.core.data.provider.QueryScript;
import datart.core.data.provider.SingleTypedValue;
import datart.data.provider.calcite.SqlNodeUtils;
import datart.data.provider.calcite.SqlParserUtils;
import org.apache.calcite.sql.*;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 支绍昆
 * 2023/4/13
 * 解析sql,根据表名添加查询条件.
 **/
public class OrgRowPermissionProcessor {

    private static final Logger log = LoggerFactory.getLogger(OrgRowPermissionProcessor.class);

    public static boolean preRun(SqlDialect sqlDialect, QueryScript script, ExecuteParam executeParam) {
        log.info("base script : {}", script.getScript());

        String origin = script.getScript();
        String sql = StringUtils.removeEnd(origin.trim(), ";");
        SqlParser parser = SqlParserUtils.createParser(sql, sqlDialect);
        try {
            SqlNode sqlNode = parser.parseQuery();
            analysis(sqlNode);
            script.setScript(sqlNode.toSqlString(sqlDialect).toString());
        } catch (SqlParseException e) {
            log.error("view sql parser fail : {}", e.getMessage());
            log.error("sql: {}", script.getScript());
        }
        return false;
    }

    private static List<SqlIdentifier> analysis(SqlNode sqlNode) {
        if (null == sqlNode) {
            return Collections.emptyList();
        }
        switch (sqlNode.getClass().getSimpleName()) {
            case "SqlSelect": {
                SqlSelect sqlSelect = (SqlSelect) sqlNode;
                analysis(sqlSelect.getSelectList());
                analysis(sqlSelect.getWhere());
                List<SqlIdentifier> identifiers = analysis(sqlSelect.getFrom());
                insertCondition((SqlSelect) sqlNode, identifiers);
            }
            break;
            case "SqlOrderBy": {
                analysis(((SqlOrderBy) sqlNode).query);
            }
            break;
            case "SqlNodeList": {
                SqlNodeList sqlList = (SqlNodeList) sqlNode;
                sqlList.getList().forEach(OrgRowPermissionProcessor::analysis);
            }
            break;
            case "SqlBasicCall": {
                SqlBasicCall sqlBasic = (SqlBasicCall) sqlNode;
                if (sqlBasic.getOperator().equals(SqlStdOperatorTable.AS)) {
                    List<SqlIdentifier> identifiers = analysis(sqlBasic.getOperands()[0]);
                    if (identifiers.isEmpty()) {
                        return identifiers;
                    } else {
                        SqlIdentifier asIdentifier = (SqlIdentifier) sqlBasic.getOperands()[1];
                        return identifiers.stream().map(e -> concat(e, asIdentifier)).collect(Collectors.toList());
                    }
                } else {
                    for (SqlNode sub : sqlBasic.getOperands()) {
                        analysis(sub);
                    }
                }
            }
            break;
            case "SqlJoin": {
                SqlJoin join = (SqlJoin) sqlNode;
                List<SqlIdentifier> identifiers = new ArrayList<>();
                identifiers.addAll(analysis(join.getLeft()));
                identifiers.addAll(analysis(join.getRight()));
                return identifiers;
            }
            case "SqlIdentifier":
                return Collections.singletonList((SqlIdentifier) sqlNode);
        }
        return Collections.emptyList();
    }

    private static void insertCondition(SqlSelect sqlSelect, List<SqlIdentifier> identifiers) {
        if (identifiers.isEmpty()) {
            return;
        }
        for (SqlIdentifier identifier : identifiers) {
            ImmutableList<String> names = identifier.names;
            SqlIdentifier active;
            if (names.size() > 1) {
                String alias = identifier.names.get(identifier.names.size() - 1);
                active = SqlNodeUtils.createSqlIdentifier(alias, "create_by");
            } else {
                active = SqlNodeUtils.createSqlIdentifier("create_by");
            }
            SqlNode value1 = SqlNodeUtils.createSqlNode(new SingleTypedValue("7016", ValueType.STRING));
            SqlNodeList values = SqlNodeList.of(value1);
            SqlBasicCall newCondition = new SqlBasicCall(SqlStdOperatorTable.IN, new SqlNode[]{active, values}, SqlParserPos.ZERO);

            if (sqlSelect.getWhere() == null) {
                sqlSelect.setWhere(newCondition);
            } else {
                SqlNode where = sqlSelect.getWhere();
                SqlBasicCall newWhere = new SqlBasicCall(SqlStdOperatorTable.AND, new SqlNode[]{where, newCondition}, SqlParserPos.ZERO);
                sqlSelect.setWhere(newWhere);
            }
        }
    }

    private static SqlIdentifier concat(SqlIdentifier left, SqlIdentifier right) {
        int lSize = left.names.size();
        int rSize = right.names.size();
        String[] names = new String[lSize + rSize];
        for (int i = 0; i < lSize; i++) {
            names[i] = left.names.get(i);
        }
        for (int i = 0; i < rSize; i++) {
            names[lSize + i] = right.names.get(i);
        }
        return SqlNodeUtils.createSqlIdentifier(names);
    }

}
